<!-- 上传图片并裁剪 -->
<template>
  <div :style="{ marginTop: marginTop + 'px' }">
    <!-- 上传 -->
    <template>
      <div
        v-show="previewImg"
        class="preview-img"
        :style="{ width: uploadWidth + 'px', height: uploadHeight + 'px' }"
        @click="refreshFn"
      >
        <img
          v-if="!isPreviewError"
          :src="previewImg"
          :style="{ width: '100%', height: '100%', objectFit: objectFit }"
          @error="handlePreviewImg"
        >
        <span v-if="errorIsShowDefaultImage && isPreviewError" class="error_default_image">
          <slot name="default" />
        </span>
      </div>

      <el-upload
        v-show="!previewImg"
        ref="upload"
        class="upload-demo"
        :style="{ width: uploadWidth + 'px', height: uploadHeight + 'px' }"
        :action="actionUrl ? actionUrl : getUploadUrl"
        :on-change="handleChangeUpload"
        :auto-upload="false"
        :show-file-list="false"
        :disabled="disabled"
      >
        <div
          class="upload-demo-icon"
          :style="{
            width: uploadWidth + 'px',
            height: uploadHeight + 'px',
            lineHeight: uploadHeight + 'px',
          }"
        >
          <i class="fa-solid fa-cloud-arrow-up avatar-uploader-icon" />
        </div>
      </el-upload>
    </template>
    <!-- 裁剪 -->
    <el-dialog
      title="图片剪裁"
      :visible.sync="dialogVisible"
      class="crop-dialog"
      append-to-body
    >
      <div style="padding: 0 20px">
        <div
          :style="{
            textAlign: 'center',
            width: cropperWidth != 0 ? cropperWidth + 'px' : 'auto',
            height: cropperHeight + 'px',
          }"
        >
          <VueCropper
            ref="cropper"
            :img="cropperImg"
            :output-size="outputSize"
            :output-type="outputType"
            :info="info"
            :full="full"
            :can-move="canMove"
            :can-move-box="canMoveBox"
            :original="original"
            :auto-crop="autoCrop"
            :can-scale="canScale"
            :fixed="fixed"
            :fixed-number="fixedNumber"
            :fixed-box="fixedBox"
            :center-box="centerBox"
            :info-true="infoTrue"
            :auto-crop-width="autoCropWidth"
            :auto-crop-height="autoCropHeight"
          />
        </div>
      </div>
      <!-- 这里的按钮可以根据自己的需求进行增删-->
      <div v-if="actionButtonFlag" class="action-box">
        <el-upload
          action="#"
          :auto-upload="false"
          :show-file-list="false"
          :on-change="handleChangeUpload"
          style="margin-right: 15px"
        >
          <el-button plain>更换图片</el-button>
        </el-upload>
        <el-button plain @click="clearImgHandle">清除图片</el-button>
        <el-button plain @click="rotateLeftHandle">左旋转</el-button>
        <el-button plain @click="rotateRightHandle">右旋转</el-button>
        <el-button plain @click="changeScaleHandle(1)">放大</el-button>
        <el-button plain @click="changeScaleHandle(-1)">缩小</el-button>
        <el-button plain @click="fixed = !fixed">{{ fixed ? '固定比例' : '自由比例' }}</el-button>
        <el-button plain @click="downloadHandle('blob')">下载</el-button>
      </div>

      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" :loading="loading" @click="finish">确认</el-button>
      </div>
    </el-dialog>
    <!-- 预览 -->
    <el-dialog
      title="图片预览"
      :visible.sync="previewDialogVisible"
      append-to-body
    >
      <img :src="previewImg" style="width: 100%">
      <div slot="footer" style="text-align: center">
        <el-button
          type="primary"
          @click="previewDialogVisible = false"
        >关 闭
        </el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { VueCropper } from 'vue-cropper'
import { getUploadUrl, uploadFile } from '@/api/file'
import { getProxyUrl } from '@/utils/proxy'
import { error } from 'autoprefixer/lib/utils'

export default {
  name: 'Cropper',
  components: {
    VueCropper
  },
  props: {
    marginTop: {
      type: Number,
      default: 0
    },
    // 上传属性

    // 图片路径
    imgSrc: {
      type: String,
      default: ''
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false
    },
    // 列表索引
    listIndex: {
      type: Number,
      default: null
    },
    // 上传路径
    actionUrl: {
      type: String,
      default: '#'
    },
    // 上传宽度
    uploadWidth: {
      type: Number,
      default: 80
    },
    // 上传高度
    uploadHeight: {
      type: Number,
      default: 80
    },
    // 图片显示角度 传值详情参考mdn object-fit
    objectFit: {
      type: String,
      default: 'fill'
    },
    errorIsShowDefaultImage: {
      type: Boolean,
      default: true
    },

    // 裁剪属性

    // 裁剪弹出框的宽度
    cropperWidth: {
      type: Number,
      default: 0
    },
    // 裁剪弹出框的高度
    cropperHeight: {
      type: Number,
      default: 400
    },
    // 裁剪生成图片的质量 0.1-1
    outputSize: {
      type: Number,
      default: 1
    },
    // 裁剪生成图片的格式
    outputType: {
      type: String,
      default: 'png'
    },
    // 裁剪框的大小信息
    info: {
      type: Boolean,
      default: true
    },
    // 是否输出原图比例的截图
    full: {
      type: Boolean,
      default: true
    },
    // 截图框能否拖动
    canMove: {
      type: Boolean,
      default: true
    },
    // 截图框能否拖动
    canMoveBox: {
      type: Boolean,
      default: true
    },
    // 上传图片按照原始比例渲染
    original: {
      type: Boolean,
      default: true
    },
    // 是否默认生成截图框
    autoCrop: {
      type: Boolean,
      default: true
    },
    // 图片是否允许滚轮缩放
    canScale: {
      type: Boolean,
      default: true
    },
    // 是否开启截图框宽高固定比例
    fixed: {
      type: Boolean,
      default: true
    },
    // 截图框的宽高比例 开启fixed生效
    fixedNumber: {
      type: Array,
      default: () => [1, 1]
    },
    // 固定截图框大小 不允许改变
    fixedBox: {
      type: Boolean,
      default: false
    },
    // 截图框是否被限制在图片里面
    centerBox: {
      type: Boolean,
      default: true
    },
    // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
    infoTrue: {
      type: Boolean,
      default: true
    },
    // 默认生成截图框宽度
    autoCropWidth: {
      type: Number,
      default: 200
    },
    // 默认生成截图框高度
    autoCropHeight: {
      type: Number,
      default: 200
    },
    // 是否出现操作按钮
    actionButtonFlag: {
      type: Boolean,
      default: true
    },
    // 裁剪路径输出格式 base64：base64； blob:blob;
    cropFormat: {
      type: String,
      default: 'blob'
    },
    // 图片最大宽度
    maxImgWidth: {
      type: Number,
      default: 200
    },
    // 图片最大高度
    maxImgHeight: {
      type: Number,
      default: 200
    }
  },
  data() {
    return {
      previewImg: '', // 预览图片地址
      dialogVisible: false, // 图片裁剪弹框
      cropperImg: '', // 裁剪图片的地址
      loading: false, // 防止重复提交
      previewDialogVisible: false, // 预览弹框
      isPreviewError: false // 预览图片是否出现错误
    }
  },
  watch: {
    imgSrc: {
      handler(newVal, oldVal) {
        this.previewImg = newVal
      },
      deep: true, // 深度监听
      immediate: true // 首次进入就监听
    }
  },
  methods: {
    error,
    getUploadUrl,
    // 上传按钮 限制图片大小和类型
    handleChangeUpload(file) {
      const isJPG =
        file.raw.type === 'image/jpeg' || file.raw.type === 'image/png'
      const isLt2M = file.size / 1024 / 1024 < 2
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG/PNG 格式!')
        return false
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!')
        return false
      }
      // 上传成功后将图片地址赋值给裁剪框显示图片
      this.$nextTick(async() => {
        // base64方式
        // this.option.img = await fileByBase64(file.raw)
        this.cropperImg = URL.createObjectURL(file.raw)
        this.loading = false
        this.dialogVisible = true
      })
    },
    // 放大/缩小
    changeScaleHandle(num) {
      num = num || 1
      this.$refs.cropper.changeScale(num)
    },
    // 左旋转
    rotateLeftHandle() {
      this.$refs.cropper.rotateLeft()
    },
    // 右旋转
    rotateRightHandle() {
      this.$refs.cropper.rotateRight()
    },
    // 下载
    downloadHandle(type) {
      const aLink = document.createElement('a')
      aLink.download = Date.now()
      if (type === 'blob') {
        this.$refs.cropper.getCropBlob((data) => {
          aLink.href = URL.createObjectURL(data)
          aLink.click()
        })
      } else {
        this.$refs.cropper.getCropData((data) => {
          aLink.href = data
          aLink.click()
        })
      }
    },
    // 清理图片
    clearImgHandle() {
      this.cropperImg = ''
    },
    // 截图
    finish() {
      if (this.cropFormat === 'base64') {
        // 获取截图的 base64 数据
        this.$refs.cropper.getCropData((data) => {
          this.loading = true
          this.dialogVisible = false

          this.getImgHeight(data, this.maxImgWidth, this.maxImgHeight).then(
            (imgUrl) => {
              // console.log(imgUrl, "base64");
              this.previewImg = imgUrl
              if (this.listIndex !== null) {
                this.$emit('successCheng', this.previewImg, this.listIndex)
              } else {
                this.$emit('successCheng', this.previewImg)
              }
            }
          )
        })
      } else if (this.cropFormat === 'blob') {
        // 获取截图的 blob 数据
        this.$refs.cropper.getCropBlob((blob) => {
          this.loading = true
          this.dialogVisible = false

          this.getImgHeight(
            URL.createObjectURL(blob),
            this.maxImgWidth,
            this.maxImgHeight
          ).then((imgUrl) => {
            this.previewImg = imgUrl
            if (this.listIndex !== null) {
              this.$emit('successCheng', this.previewImg, this.listIndex)
            } else {
              this.$emit('successCheng', this.previewImg)
            }
          })

          this.uploadImage(blob)
        })
      }
      this.isPreviewError = false
    },
    // 上传到后端
    uploadImage(blob) {
      const file = new File([blob], new Date() + '.png', {
        type: 'image/png',
        lastModified: Date.now()
      })
      file.uid = Date.now()
      var fd = new FormData()
      fd.append('file', file, new Date() + '.png')

      uploadFile(fd).then((res) => {
        if (res.code === 200) {
          const imageUrl = getProxyUrl() + res.data.url
          this.$emit('input', imageUrl)
        }
      })
    },
    // 更换图片
    refreshFn() {
      this.$refs['upload'].$refs['upload-inner'].handleClick()
    },
    // 获取图片高度并修改
    getImgHeight(imgSrc, scaleWidth = 648, scaleHeight = 1152) {
      return new Promise((resolve, reject) => {
        const img = new Image() // 创建一个img对象
        img.src = imgSrc // 设置图片地址
        let imgUrl = '' // 接收图片地址
        img.onload = () => {
          if (img.width > scaleWidth || img.height > scaleHeight) {
            const canvas = document.createElement('canvas')
            const context = canvas.getContext('2d')
            canvas.width = scaleWidth
            canvas.height = scaleHeight
            context.drawImage(img, 0, 0, scaleWidth, scaleHeight)
            if (this.cropFormat === 'blob') {
              imgUrl = this.base64toBlob(
                canvas.toDataURL('image/png', 1),
                'image/png'
              )
            } else {
              imgUrl = canvas.toDataURL('image/png', 1)
            }
            resolve(imgUrl)
          } else {
            imgUrl = imgSrc
            resolve(imgUrl)
          }
        }
      })
    },
    handlePreviewImg() {
      console.log('预览图片加载失败')
      console.log(this.previewImg)
      this.isPreviewError = true
    },
    // base64转blob
    base64toBlob(base64, type = 'application/octet-stream') {
      // 去除base64头部
      const images = base64.replace(/^data:image\/\w+;base64,/, '')
      const bstr = atob(images)
      let n = bstr.length
      const u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return URL.createObjectURL(new Blob([u8arr], { type }))
    }
  }
}
</script>

<style lang="scss" scoped>
.preview-img {
  position: relative;
  cursor: pointer;
  border-radius: 6px;
  border: 1px solid #ededed;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #C0C4CC;

  img {
    border-radius: 4px;
  }

  .error_default_image {
    font-size: 60%;
    color: #fff;
    border-radius: 4px;
    line-height: 0;

    i {
      font-size: 2rem;
    }

    img {
      width: 100%;
      height: 100%;
      border-radius: 4px;
    }
  }
}

.upload-demo {
  border: 1px dashed #d9d9d9;
  cursor: pointer;
  border-radius: 6px;
}

.upload-demo-icon {
  font-size: 18px;
  color: #c0c0c0;
  text-align: center;
}

.crop-dialog {
  .action-box {
    margin: 20px;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;

    button {
      margin-top: 15px;
    }
  }

  .dialog-footer {
    text-align: center;

    button {
      width: 100px;
    }
  }
}
</style>
